Xceed .NET Libraries Documentation
Welcome to Xceed Data Manipulation Compoents for .NET and .NET Standard / Basic Concepts / SFTP Capabilities / Remote Command Execution

    Remote Command Execution

    Description

    The SSH protocol allows for the remote execution of commands on the server. This is done using a SSH logical channel. Many SSH features like SFtp and remote command execution are implemented over such channels. Every channel is multiplexed into a single encrypted SSH connection.

    As such, a remote command execution can be issued while a SFtp session is performing work since a new channel will be created to issue the command.

    Remote command execution works by the client sending a message that requests the server start the execution of a given command string. The 'command' string may contain a path and arguments. It is also possible to pass environment variables to the server before the command is executed.

    It should be noted that servers will take normal precautions to prevent the execution of unauthorized commands. At the very least, this means that the right to execute a command, and which commands are allowed will depend on the user that is authenticated on the SSH connection. These rights are determined by the server's administrator and cannot be computed on the client side.

    If a user doesn't have the rights to execute a command, an exception will be thrown but the SSH connection will not be closed. Other channels (like SFtp sessions) will continue to proceed without issue.

    Usage

    A remote command is executed using the Xceed.SSH.Client.ExecuteCommandSession Class. Its constructor needs to be supplied a SSHClient object that is connected and authenticated. Then, the Connect() method can be called, specifying the command to execute and, optionally, environment variables to set on the server before the command is executed.

    The ExecuteCommandSession class is ony available with the Xceed SFtp for .NET that is compiled for .NET 4.0 and later. The class is available with Xceed SFtp for Xamarin, all versions.

    The Connect() method returns immediately if the server accepts the command. If not, a SSHChannelRequestFailedException is thrown. The command is run on the server while control continues normall on the client side. The server might send standard output and/or standard error text output to the client. It is possible to catch that data with a Stream object using the GetOutputStream() and GetErrorStream() methods. It is also possible to send text to the command's standard input using a stream with the GetInputStream() method. With all these streams, it is often useful to wrap them around a System.Text.StreamReader or System.Text.StreamWriter object to read and write text lines. See the examples below for details.

    The ExecuteCommandSession class contains a CommandCompleted Event event that will be triggered when the command has completed on the server. At that point, the channel will have been closed but the possible exit status or exit signal (not all servers supply these values) will still be available to be read.

    Another way to watch for the completion of the command is to wait on the SessionCompletedWaitHandle.

    The ExecuteCommandSession class implements the IDisposable interface. It is good practice to dispose of an instance of the class once it is no longer needed.

    Examples

     Execute and wait for result

    The simplest way to use the ExecuteCommandSession class is to start the command and wait for the command to complete.

    using System.IO;
    
    using Xceed.SSH.Client;
    using Xceed.SSH.Core;
    using Xceed.SSH.Protocols;
    using Xceed.FileSystem;
    
    namespace DocumentationExamples.SSH
    {
      public class ExecuteCommandSession3
      {
        public void Example()
        {
          string host = "localhost";
          string username = "normal1";
          string password = "normal1";
    
          SSHClient ssh = new SSHClient();
    
          // Connect to the host
          ssh.Connect( host );
    
          try
          {
            // Log in
            ssh.Authenticate( username, password );
    
            // The remote command to execute
            string command = "dir";
    
            // Create a session that will execute a remote command
            using( ExecuteCommandSession executeCommandSession = new ExecuteCommandSession( ssh ) )
            {
              // Execute the command on the remote server, waiting until it completes
              Nullable<int> exitStatus = executeCommandSession.ExecuteCommand( command );
    
              /* Some servers will return exit information like the return code and the 'signal'
                 if the command fails. */
    
              Console.WriteLine( "Command exited with status {0}", exitStatus.HasValue ? exitStatus.ToString() : "(null)" );
    
              SSHChannelExitSignal exitSignal = executeCommandSession.ExitSignal;
              if( exitSignal != null )
              {
                Console.WriteLine( "Command exited with signal {0}: {1}", exitSignal.SignalName, exitSignal.ErrorMessage );
              }
            }
          }
          catch( SSHChannelRequestFailedException )
          {
            /* This exception is thrown by ExecuteCommandSession.Connect(). The most common
             * cause is the authenticated user does not have the rights to execute commands
             * on the server. */
          }
          finally
          {
            // Always make sure to disconnect from the server when the connection is no longer needed
            ssh.Disconnect();
          }
        }
      }
    }
     Fire-and-forget

    The ExecuteCommandSession class can also be used in a fire-and-forget manner.

    using System.IO;
    using System.Threading.Tasks;
    
    using Xceed.SSH.Client;
    using Xceed.SSH.Core;
    using Xceed.SSH.Protocols;
    using Xceed.FileSystem;
    
    namespace DocumentationExamples.SSH
    {
      public class ExecuteCommandSession4
      {
        public void Example()
        {
          string host = "localhost";
          string username = "normal1";
          string password = "normal1";
    
          // The remote command to execute
          string command = "dir";
    
          SSHClient ssh = new SSHClient();
    
          // Connect to the host
          ssh.Connect( host );
    
          try
          {
            // Log in
            ssh.Authenticate( username, password );
    
            Task<Nullable<int>> task = this.ExecuteRemoteCommandAsync( ssh, command );
    
            /* TODO: Perform other business that does not require the result of the remote command */
    
            /* Now that we need the result from the remote command, we will wait for it */
            Nullable<int> exitStatus = task.Result;
            Console.WriteLine( "Command exited with status {0}", exitStatus.HasValue ? exitStatus.ToString() : "(null)" );
          }
          finally
          {
            // Always make sure to disconnect from the server when the connection is no longer needed
            ssh.Disconnect();
          }
        }
    
        private async Task<Nullable<int>> ExecuteRemoteCommandAsync( SSHClient ssh, string command )
        {
          Task<Nullable<int>> executeCommandTask;
          Nullable<int> exitStatus;
    
          ExecuteCommandSession executeCommandSession = null;
    
          // Create a session that will execute a remote command
          executeCommandSession = new ExecuteCommandSession( ssh );
    
          try
          {
            // Start executing the specified command on the remote server
            executeCommandTask = executeCommandSession.ExecuteCommandAsync( command );
          }
          catch( SSHChannelRequestFailedException )
          {
            /* This exception is thrown by ExecuteCommandSession.Connect(). The most common
              * cause is the authenticated user does not have the rights to execute commands
              * on the server. */
    
            /* The 'CommandCompleted' event is not triggered when the request to launch the command fails */
    
            // Dispose of the session to free resources
            executeCommandSession.Dispose();
    
            // Rethrow the exception
            throw;
          }
    
          // Wait for the remote command to complete while allowing the calling method to continue execution
          exitStatus = await executeCommandTask;
    
          /* This code will be executed in a background thread from ThreadPool */
    
          // Dispose of the session to free resources
          executeCommandSession.Dispose();
    
          return exitStatus;
        }
      }
    }
     Capture command output

    The output of the command can be captured. Here a technique using asynchronous reading is used in order to read output and error text at the same time.

    Also shown in this example is how to pass environment variables to the server.

    using System.Collections.Generic;
    using System.IO;
    using System.Threading;
    using System.Threading.Tasks;
    
    using Xceed.SSH.Client;
    using Xceed.SSH.Core;
    using Xceed.SSH.Protocols;
    using Xceed.FileSystem;
    
    namespace DocumentationExamples.SSH
    {
      public class ExecuteCommandSession1
      {
        public void Example()
        {
          string host = "localhost";
          string username = "normal1";
          string password = "normal1";
    
          SSHClient ssh = new SSHClient();
    
          // Connect to the host
          ssh.Connect( host );
    
          try
          {
            // Log in
            ssh.Authenticate( username, password );
    
            // The remote command to execute
            string command = "dir *.*";
    
            // Some environment variables
            KeyValuePair<string, string> environmentVariable = new KeyValuePair<string, string>( "MYVARIABLE", "MYVALUE" );
    
            // Create a session that will execute a remote command
            using( ExecuteCommandSession executeCommandSession = new ExecuteCommandSession( ssh ) )
            {
              Stream commandOutputStream = null;
              Stream commandErrorStream = null;
    
              try
              {
                /* We obtain the output and error streams BEFORE starting the command so that we don't
                   miss any output. */
    
                /* If the CommandOutputStream or the CommandErrorStream object is not taken,
                   the component silently discards the incoming data. This might be desirable if
                   the output and/or error text is not needed. */
    
                // Get the command's output stream (standard output)
                commandOutputStream = executeCommandSession.GetOutputStream();
    
                // Get the command's error stream (standard error)
                commandErrorStream = executeCommandSession.GetErrorStream();
    
                /* Now that we are setup the way we want, we can safely start the remote command */
    
                // Start executing the specified command on the remote server passing the supplied environment variables to the server
                executeCommandSession.Connect( command, environmentVariable );
    
                /* Notice how we wrap the streams with StreamReader objects only after starting the command.
                   This guards against the possibility of the StreamReader object calling Stream.Read() before
                   we've started the command. */
    
                // Wrap a stream reader around the output stream
                System.IO.StreamReader outputReader = new System.IO.StreamReader( commandOutputStream );
    
                // Wrap a stream reader around the error stream
                System.IO.StreamReader errorReader = new System.IO.StreamReader( commandErrorStream );
    
                // Read a line from the output and error at the same time
                Task<string> readOutputLineTask = outputReader.ReadLineAsync();
                Task<string> readErrorLineTask = errorReader.ReadLineAsync();
    
                string line;
    
                // While either the output or error streams haven't reached end-of-file
                while( readOutputLineTask != null || readErrorLineTask != null )
                {
                  // If reading an output line has completed
                  if( readOutputLineTask != null && readOutputLineTask.IsCompleted )
                  {
                    // Get the line
                    line = readOutputLineTask.Result;
    
                    // If we have a line
                    if( line != null )
                    {
                      // Output it to the console
                      Console.WriteLine( line );
    
                      // Read another line
                      readOutputLineTask = outputReader.ReadLineAsync();
                    }
                    else
                    {
                      /* We've reached end-of-file. We don't want to read again */
                      readOutputLineTask = null;
                    }
                  }
    
                  // If reading an error line has completed
                  if( readErrorLineTask != null && readErrorLineTask.IsCompleted )
                  {
                    // Get the line
                    line = readErrorLineTask.Result;
    
                    // If we have a line
                    if( line != null )
                    {
                      // Output it to the console
                      Console.WriteLine( line );
    
                      // Read another line
                      readErrorLineTask = errorReader.ReadLineAsync();
                    }
                    else
                    {
                      /* We've reached end-of-file. We don't want to read again */
                      readErrorLineTask = null;
                    }
                  }
                }
    
                /* Because we've processed the output stream until end-of-file, we are sure that
                  the command has completed. No need to wait. 
    
                   However, if accessing the exit status or possible exit signal of the command
                   is important to the application, it is best to wait until the command has signaled it
                   has completed. At that point, the exit information will have been received. */
    
                // Wait until the command has completed
                executeCommandSession.SessionCompletedWaitHandle.WaitOne();
    
                /* Some servers will return exit information like the return code and the 'signal'
                  if the command fails. */
    
                Nullable<int> exitStatus = executeCommandSession.ExitStatus;
                Console.WriteLine( "Command exited with status {0}", exitStatus.HasValue ? exitStatus.ToString() : "(null)" );
    
                SSHChannelExitSignal exitSignal = executeCommandSession.ExitSignal;
                if( exitSignal != null )
                {
                  Console.WriteLine( "Command exited with signal {0}: {1}", exitSignal.SignalName, exitSignal.ErrorMessage );
                }
              }
              catch( SSHChannelRequestFailedException )
              {
                /* This exception is thrown by ExecuteCommandSession.Connect(). The most common
                 * cause is the authenticated user does not have the rights to execute commands
                 * on the server. */
              }
              finally
              {
                if( commandOutputStream != null )
                {
                  commandOutputStream.Close();
                  commandOutputStream = null;
                }
    
                if( commandErrorStream != null )
                {
                  commandErrorStream.Close();
                  commandErrorStream = null;
                }
              }
            }
    
          }
          finally
          {
            // Always make sure to disconnect from the server when the connection is no longer needed
            ssh.Disconnect();
          }
        }
      }
    }
     Send data to standard input

    The output of the command can be captured. Here a technique using asynchronous reading is used in order to read output and error text at the same time.

    using System.IO;
    
    using Xceed.SSH.Client;
    using Xceed.SSH.Core;
    using Xceed.SSH.Protocols;
    using Xceed.FileSystem;
    
    namespace DocumentationExamples.SSH
    {
      public class ExecuteCommandSession2
      {
        public void Example()
        {
          string host = "localhost";
          string username = "normal1";
          string password = "normal1";
    
          SSHClient ssh = new SSHClient();
    
          // Connect to the host
          ssh.Connect( host );
    
          try
          {
            // Log in
            ssh.Authenticate( username, password );
    
            // The remote command to execute
            string command = "copy Test.txt BackTest.txt /-Y";
    
            // Create a session that will execute a remote command
            using( ExecuteCommandSession executeCommandSession = new ExecuteCommandSession( ssh ) )
            {
              // Get the command's output stream (standard output/stdout)
              Stream commandOutputStream = executeCommandSession.GetOutputStream();
    
              // Start executing the specified command on the remote server
              executeCommandSession.Connect( command );
    
              // Wrap the output stream into a stream reader
              System.IO.StreamReader reader = new System.IO.StreamReader( commandOutputStream );
    
              string line;
    
              /* We expect to receive the text
    
                 Overwrite BackTest.txt? (Yes/No/All):
    
                 and then the remote console will wait for input. We can't call ReadLine() to get
                 the question since it does not contain a newline, we would wait forever.
    
                 Since we know what we're going to receive, we'll just go-ahead and send the response immediately. */
    
              // Get the command's input stream (standard input/stdin)
              Stream commandInputStream = executeCommandSession.GetInputStream();
    
              // Wrap the input stream into a stream writer
              StreamWriter writer = new StreamWriter( commandInputStream );
    
              // Answer the overwrite query
              writer.WriteLine( "y" );
              writer.Flush();
    
              // Read the next lines until we reach end-of-file
              while( ( line = reader.ReadLine() ) != null )
              {
                // Output the line
                Console.WriteLine( line );
              }
            }
          }
          catch( SSHChannelRequestFailedException )
          {
            /* This exception is thrown by ExecuteCommandSession.Connect(). The most common
             * cause is the authenticated user does not have the rights to execute commands
             * on the server. */
            }
          finally
          {
            // Always make sure to disconnect from the server when the connection is no longer needed
            ssh.Disconnect();
          }
        }
      }
    }